Skip to main content

第 4 课:P2P 网络

在我们上面讲到的 IP 访问和域名访问,使用的都是 本地设备+服务器(Client-Server) 的架构。一台中心服务器处理多个本地设备的访问请求,因此是属于中心化的网络结构。

而 P2P(Peer-to-Peer,点对点连接)网络通信模型,强调每个节点(peer)地位平等、直接通信不依赖中心服务器

P2P = 一台设备直接连接另一台设备,进行数据传输

与传统的“客户端-服务器”(Client-Server)架构不同,P2P 没有中心节点。每个 peer 都是“客户端 + 服务器”的双重身份。

模型特点
Client-Server所有请求都通过中心服务器转发(如网站、游戏服务器)
P2P每个节点之间可以直接通信(如 BT、区块链)

应用例子如:

应用场景说明
BitTorrent下载文件时,你下载别人的同时也在上传给别人(分布式)
区块链比特币/以太坊等所有节点直接同步数据,无中心服务器
WebRTC浏览器之间的视频聊天、屏幕共享(如 Google Meet 的底层)
游戏联机有些局域网或小型在线游戏是玩家之间直接连接

虽然理念简单,但实际应用时主要要解决三个技术难点

  1. 大多数用户都在 NAT/CGNAT 后面,没有公网 IP,如何互相直接连接?

  2. 没有中心验证机制 → 极易伪造、污染数据?

  3. Peer 可能随时上线下线,如何保证可用性?

让我们依次来解答这些问题


(一)连接建立流程

先来看下图中,设备 A 如何和设备 C 建立 P2P 连接

image-20250925155210173

设备 A 和设备 C 分别位于两个不同 NAT 后面的局域网中

  • 设备 A: IP 为 192.168.0.2,通过 路由器1(NAT1,公网出口 100.64.0.3)访问外网。
  • 设备 C: IP 为 192.168.0.2,通过 路由器2(NAT2,公网出口 100.64.0.4)访问外网。
  • ISP: 两个 NAT 的上游 ISP 地址为 100.64.0.1,ISP 公网 IP 是 142.142.142.142
  • 公共服务器: IP 为 3.152.6.45,部署了 P2P 协商服务(信令服务器 + STUN + TURN)

因为 A 和 C 都在 NAT 后面,互相不知道对方的真实出口 IP:port,不能直接向对方发送 UDP/TCP 数据包,因此必须借助一个中立的中继服务器,称为 Signaling Server。

注意:Signaling Server 只是传话筒,不参与后续数据传输。


步骤一:注册身份

首先 设备A 要在 Signaling Server 上注册身份(类似登录),A 会发送:

{
  "type": "register",
  "user": "deviceA"
}

Signaling Server 回复“注册成功”:

{
  "type": "register-success",
  "user": "deviceA"
}

此时,A 已注册为 "deviceA",等待匹配。于是 A 向服务器发起连接请求:

{
  "type": "connect-request",
  "from": "deviceA",
  "to": "deviceC"
}

步骤二:连接协商

假设 设备 C 也在这台服务器上注册过了,于是 Signaling Server 将 A 的请求转发给 C:

{
  "type": "incoming-request",
  "from": "deviceA"
}

C 收到该请求后,回复 Signaling Server C ,表示同意连接

{
  "type": "connect-accept",
  "from": "deviceC",
  "to": "deviceA"
}

步骤三:交换连接方式

现在 A 知道 C 已经答应了连接请求,那么就需要告诉 C 接下来自己通信准备采用的方式,例如使用的编码格式,加密方式等等。这个其实叫做 SDP(Session Description Protocol) ,包含了编解码器(H.264, Opus等)、媒体类型(audio, video)、ICE 候选(Candidate 地址)、DTLS 加密参数等。A 发送 SDP offer,经过 Signaling Server 转发,发送给 C

{
  "type": "sdp-offer",
  "from": "deviceA",
  "to": "deviceC",
  "sdp": {
    "type": "offer",
    "sdp": "v=0\no=alice 2890844526 2890844526 IN IP4 192.0.2.1\ns=-\nt=0 0\n..."
  }
}

C 收到 offer,发送 SDP answer

{
  "type": "sdp-answer",
  "from": "deviceC",
  "to": "deviceA",
  "sdp": {
    "type": "answer",
    "sdp": "v=0\no=bob 283903 283903 IN IP4 192.0.2.2\ns=-\nt=0 0\n..."
  }
}

此时 A、C 双方都知道彼此支持的媒体格式和连接方式。


步骤四:交换连接渠道

SDP(Session Description Protocol) 中有一条重要的信息叫做 ICE 候选(Candidate 地址),这是指 A、C 除了通过这台 Signaling Server 中继之外,还可以通过什么渠道互相连接。例如:

设备 A 计划使用 733 端口进行P2P通信,则之前就已经用 733 端口向 Signaling Server 发起 Websocket 连接,同时 192.168.0.2:733 被家庭路由器 NAT 映射为 100.64.0.3:65000,由进一步被 ISP 路由器 NAT映射为 142.142.142.142:89000,流程如下。

`192.168.0.2:733` <--> `100.64.0.3:65000` <--> `142.142.142.142:89000` <--> `3.152.6.45`

那么接下来 A 发给 C 的是:142.142.142.142:89000,以后 C 就可以用 这个地址直接联系到 A,不需要通过中间服务器。

这种连接方式也称为 NAT 打洞,或者 UDP 打洞,或者 STUN 打洞


事实上, A 给 C 的连接方式不仅只有这种 NAT 映射路径,还有其他连接方式,它们都是 ICE 候选(Candidate 地址)

ICE CandidateICE 协议中的一个关键概念,它描述了“某个设备可能被连接到的网络路径和地址”。一个 candidate 就像是对外广播的名片:“我可以通过这个地址被别人连接!”

ICE 协议(Interactive Connectivity Establishment)会收集所有可能连接的方式(如 NAT 外部端口、STUN地址、Host地址等)

所以 Candidate 的“种类”有三个:

类型说明条件代价举例
Host内网地址AC 处于相同子网192.168.0.2:733
Server Reflexive经过 NAT 映射后的公网地址NAT 支持对称打洞142.142.142.142:89000
Relay通过 TURN 中继服务器永远可以relay.turnserver.com:54789

因此 A 给 C 的 ICE Candidate 为:

# Host Candidate(低代价,内网地址)
{
  "type": "ice-candidate",
  "from": "deviceA",
  "to": "deviceC",
  "candidate": {
    "candidate": "candidate:842163049 1 udp 1677729535 192.168.0.2 733 typ host",
    "sdpMid": "0",
    "sdpMLineIndex": 0
  }
}

# Server Reflexive Candidate(中代价,NAT 映射地址)
{
  "type": "ice-candidate",
  "from": "deviceA",
  "to": "deviceC",
  "candidate": {
    "candidate": "candidate:123456789 1 udp 1677724415 142.142.142.142 89000 typ srflx raddr 192.168.0.2 rport 733",
    "sdpMid": "0",
    "sdpMLineIndex": 0
  }
}

# Relay Candidate(高代价,TURN 中继)
{
  "type": "ice-candidate",
  "from": "deviceA",
  "to": "deviceC",
  "candidate": {
    "candidate": "candidate:987654321 1 udp 1677719551 relay.turnserver.com 54789 typ relay raddr 192.168.0.2 rport 733",
    "sdpMid": "0",
    "sdpMLineIndex": 0
  }
}

解释:

  • candidate:842163049 是一个 foundation 和 component ID,可保持不变

  • typ host 表示这是 host 类型

  • typ srflx 表示这是一个 Server Reflexive 类型

  • typ relay 表示中继类型

  • raddr / rport 表示此 candidate 原始来源地址是内网的 192.168.0.2:733

  • relay.turnserver.com 54789 是通过 TURN 中继服务器获取的转发地址


在 C 拿到 A 的 ICE Candidate 后,就会去维护一个类似这样的列表:

[
  {
    "type": "host",
    "candidate": "192.168.0.2:733"
  },
  {
    "type": "srflx",  // server reflexive
    "candidate": "142.142.142.142:89000"
  },
  {
    "type": "relay",  // TURN 中继地址(可选)
    "candidate": "23.56.1.10:45789"
  }
]

依次尝试去连接

  • 首选尝试 host ↔ host(在局域网内可行)
  • 不行再试 srflx ↔ srflx(NAT打洞)
  • 最后保底用 relay ↔ relay(必须中继)

这就是 **ICE 优选(ICE connectivity checks)**的过程。


至此为止,A 和 C 均掌握对方的候选地址。接下来,使用 ICE 协议进行连接测试与优选,并建立 DTLS 安全通道。Signaling Server 至此退出,不再参与通信。

**思考: A C 如何知道自己的三个ICE Candidate **

注意,Signaling Server 只负责传话,不负责记录,因此A 的 NAT 穿透地址等 连接渠道不是 Signaling Server记录的,而是作为一项数据直接写在 A 发给 Signaling Server 的数据包中。那 A 是怎么知道自己的 NAT 出口地址的呢?

  1. 设备 A 通过本地网卡拿到 host candidate(如 192.168.0.2:733
  2. A 再向 STUN 服务器发送请求,它会返回 NAT 映射地址 142.142.142.142:89000
  3. A 可能还会连接 TURN 服务器获得 relay 地址(可选)

STUN(Session Traversal Utilities for NAT)服务器告诉客户端:你“看起来”来自什么公网 IP 和端口,例如你向一个 STUN 服务器(比如 stun.l.google.com:19302)发一个包,对方回复说:

“你在我这看来是从 142.142.142.142:89000 发过来的”

你就知道了你的 NAT 映射地址,可以把它作为 ICE Candidate 发给通信对方


(二) NAT 的穿透难度

Server Reflexive 通信渠道并不是一直可用的。不同的 NAT 对穿透支持程度不同,分以下几种类型

NAT 类型行为特征穿透性(UDP打洞)示例行为
Full Cone NAT内部 IP+端口 映射后,任何外部主机都可以通过这个映射地址联系到你✅ 非常容易192.168.0.2:7331.2.3.4:9000,任何人只要知道 1.2.3.4:9000 都能连你
Restricted Cone NAT只有你主动访问过的目标 IP 才能回连你⚠️ 可穿透如果你发了 UDP 给 3.3.3.3,那么只有 3.3.3.3 可以访问你
Port Restricted Cone NAT只有你主动访问的 IP+端口 才能连你⚠️ 穿透更难比如你访问了 3.3.3.3:5000,则只接受这个地址
Symmetric NAT每一个外部目标(IP+端口)都会触发不同的 NAT 映射端口❌ 无法打洞,只能用 TURN访问 3.3.3.3:5000 时被分配的NAT出口是 1.2.3.4:9000,访问 3.3.3.3:6000 是时被分配的 NAT 出口是1.2.3.4:9001,候选地址无法通用

例如一台在 Symmetric NAT 环境中的机器,通过访问 STUN 服务器 3.3.3.3:1234,A 知道了的自己的 NAT 公网出口是 143.231.34.12:4321。但是问题是,143.231.34.12:4321 只允许来自 3.3.3.3:1234 的连接,而不允许来自 C 的 NAT 公网出口 177.3.45.32:1145的连接,因此 C 无法用 NAT 打洞连接上 A。


而对于 Port Restricted Cone NATRestricted Cone NAT,并不完全如此。他们可以通过这种方式建立联系:

  • A 使用 STUN 服务器获取到了 143.231.34.12:4321
  • C 也获取了自己的 srflx 地址 155.66.77.88:5678
  • A 向 C 的地址发送一个探测包
  • 此时,A 的 NAT 将允许从 155.66.77.88:5678 发来的返回包。
  • 同理,C 向 A 发包,A 的 NAT 也打开 pinhole。
  • 如果两边时机对上,打洞成功!

这正是 WebRTC + ICE 的核心原理之一:双向并发发包,尽量在 NAT 映射短时间内交汇。


Symmetric NAT 之所以不能用这个方法,是因为 A 使用 STUN 服务器获取到的 NAT 出口 143.231.34.12:4321仅对STUN 服务器 有效。A 去向 C 的地址发包的时候,实际上被分配了一个新端口(如143.231.34.12:4444),而通过中继服务器发给 C 的连接地址其实是 143.231.34.12:4321,因此 C 不能连接上 A。

大部分家庭宽带或校园网都是 Port Restricted 或 Symmetric NAT。这就是为什么在校园网或者家庭 IP 中经常无法 P2P 连接。


想要知道自己处于什么环境,可以 使用 Trickle ICE ,这是一个 WebRTC 的网页测试工具,打开后输入 ICE 服务器:

stun:stun.l.google.com:19302

点击 “Gather candidates”,观察候选地址(candidates)类型

  • 若只出现 host,说明你在同一网段或局域网(未穿透 NAT)
  • 出现 srflx:说明 STUN 成功,你不是 Symmetric NAT(可能是 Full Cone 或 Restricted)
  • 若只出现 relay 才能通信,说明是 Symmetric NAT(需要 TURN)

image-20250926164345237

如果你希望更专业一点,可以在 Linux/Mac 下使用开源工具测试:

git clone https://github.com/mystfit/nat-type-collector.git
cd nat-type-collector
go build  # 注意:需安装 Go 环境
./nat-type-collector

还有一个问题,STUN 是如何判断 NAT 类型的?

STUN 协议中通过发送三种 Binding Request 来测试 NAT 类型:

测试编号请求行为判断依据
Test I普通 Binding Request是否返回公共 IP
Test II更改来源地址和端口判断是否是 Full Cone
Test III更改目的 IP判断是否是 Symmetric

通过这三次测试组合,可以判断你的 NAT 类型。



(三)BitTorrent 点对点连接

上面我们提到,磁力链接下载也是一种 P2P 连接。

什么是磁力链接

一般来说,我们下载软件用的是 直链,比如B 站下载地址 https://dl.hdslb.com/mobile/fixed/bili_win/bili_win-install.exe?v=1.17.2-4。这种链接由中心服务器运营管理,直接指向服务器上的某个资源文件。下载的人越多,带宽占用就越大,下载越慢。

磁力链接(magnet) 则不一样,例如一份哪吒二的资源文件 magnet:?xt=urn:btih:2FAFA93452926F66AE699F3A488CB318572F92E9,这种链接是去中心化的,没有统一的服务器。每个设备通过客户端软件,加入 DHT 网络(后面会解释),在这个网络中,该设备 既扮演服务器又扮演客户端的角色

例如,设备A 加入了一个 DHT 网络,其中 设备B设备C也在这个网络中。设备A设备B 请求获取一份资源文件,但是 设备B 检查发现自己没有这个资源文件,于是把这个请求转发给自己的邻居 设备C设备C 有这个资源文件,因此和 设备A 建立起了 P2P 通信,把文件传给 设备A

由此可见,一份文件被保存的越多,越容易在 DHT 网络中被找到,下载速度越快。而P2P连接基本上不存在高并发的问题,因为基本上不会有人公用一条线路。

由于P2P去中心化难以监管的特性,经常被用来分享一些非法的文件。同时一些稀有资源如果只有少数节点有,在这些节点下线时就会难以找到,甚至无法找到这些资源。


回到上面那个例子,什么是 DHT?设备A 是如何成功连接 设备C的?

用户点击 magnet:?xt=urn:btih:<infohash>&tr=... → 客户端得到 infohash(种子唯一标识)→ 从 DHT(Kademlia) 和/或 Tracker 找 peers → 与 peers 建立 BitTorrent 握手(TCP 或 uTP/UDP)→ 用 ut_metadata(元数据扩展)或从 tracker/webseed 获取 .torrent 元数据 → 握手后交换 piece(切片),采用 rarest-first + tit‑for‑tat 策略下载直到完成。

  • magnet URI:只包含种子标识(infohash)与可选 tracker/webseed,不包含 .torrent 文件。
  • infohash:对 torrent 的 info 字段做 20 字节 SHA1(或 Base32/Hex 表示),是这份内容的唯一 ID。
  • Tracker:集中式服务器,维护一个 peer 列表(HTTP/UDP 协议)。
  • DHT:分布式哈希表(一般是 Kademlia),去中心化查找 peers(使用 KRPC 协议,UDP)。
  • Peer:运行 BitTorrent 客户端的节点(IP:port)。
  • Peer ID:每个客户端的 20 字节标识,用于识别客户端软件/版本。
  • BitTorrent 握手:客户端与 peer 建立连接后的第一个消息,包含 infohash 与 peer_id。
  • BEP / 扩展:BitTorrent enhancement proposals(例如 ut_metadata 是 BEP‑9,扩展握手是 BEP‑10)。
  • uTP:基于 UDP 的替代传输协议(micro transport protocol,用于拥塞控制)。
  • Webseed:HTTP(s) 服务器作为文件来源(非 P2P,但可作为补充)。

步骤一:客户端解析链接

我们现在先从 磁力链接的第一步:点击 magnet 链接 → 客户端解析 开始,一步步解析 BitTorrent 网络中 DHT 网络P2P 建连流程

magnet URI 是一种以 magnet:?xt=urn:btih: 开头的特殊链接格式,用于表示资源的位置 不是 一个固定的 HTTP 下载地址,而是一个 内容的哈希值(infohash),由全网 P2P 用户协作下载。

magnet:?xt=urn:btih:ABCDEF1234567890ABCDEF1234567890ABCDEF12&dn=ubuntu.iso&tr=udp://tracker.opentrackr.org:1337/announce&ws=http://webseed.example/file
参数含义
xt=urn:btih:...xt 是 "exact topic",btih 是 BitTorrent Info Hash,代表唯一资源ID。
dn=...dn 是 display name,表示资源的可读名称。
tr=...tr 是 tracker 的地址,帮助发现其他节点的辅助方法(非必要)。
ws=as=webseed(可选)

.torrent 文件是 BitTorrent 协议中用于元数据描述的文件,本身不包含内容数据,而是包含以下信息:

- announce: tracker 的 URL
- info:
- name: 文件或文件夹名
- length / files: 单个或多个文件的大小、路径
- piece length: 每一块的大小(如 512KB)
- pieces: 所有文件 hash 后拼成的哈希串

你可以把 .torrent 文件想象成一本书的目录,告诉你文件有哪些、切成多少块、每块的哈希值是多少,用于校验和定位内容

🚀 .torrent 文件在 P2P 下载流程中扮演什么角色?

🔁 两种主要使用方式:

下载方式是否需要 .torrent 文件说明
Tracker 模式✅ 需要客户端通过 .torrent 中指定的 Tracker 寻找其他节点
DHT 模式(磁力链)❌ 初始时不需要只需要 infohash,客户端通过 DHT 网络查找 .torrent 的 peers

🔗 那磁力链接怎么运作的?还需要 .torrent 文件吗?

🧲 磁力链接(magnet link)格式如下:

magnet:?xt=urn:btih:<infohash>&dn=<name>&tr=<tracker>
  • xt=urn:btih:<infohash>:这个是 .torrent 文件中 info 部分的 hash(称为 infohash)
  • dn=<name>:文件名(可选)
  • tr=<tracker>:Tracker 地址(可选)

💡 所以说:

磁力链接中不包含 .torrent 文件,但它的 infohash 就是从 .torrent 文件中计算出来的!

于是,DHT 网络可以通过这个 infohash 找到其他拥有该 .torrent 文件和数据的 peer,并从他们那里下载 .torrent 的元数据和数据本体。


📁 每个 DHT 节点里都有 .torrent 文件吗?

不是所有 DHT 节点都持有 .torrent 文件,但:

  • 如果某个 peer 正在做种(seeding),它一定拥有 .torrent 文件和实际数据。
  • 一旦你的客户端从某个 peer 获取了 .torrent 元数据,就可以开始下载整个内容了。
  • 这一步称为:metadata exchange,也叫做 BEP-9 扩展协议。

🧠 总结:.torrent 与磁力链接的区别

项目.torrent 文件Magnet 磁力链接
包含内容文件结构、块大小、哈希、Tracker等仅包含 infohash
起始方式需本地或网站下载 .torrent只需链接
查找方式Tracker / DHTDHT(必要),可选 Tracker
速度快(可直接开始)稍慢(需通过 DHT 找 .torrent)
是否离线可用✅ 本地保存即可下载❌ 需要联网找 peers

🧩 所以什么时候会用到 .torrent 文件?

  • 传统 BT 网站:用户下载 .torrent 文件后用客户端打开(如 qBittorrent、Transmission)。
  • 磁力链接启动时:客户端先通过 DHT 找到持有 .torrent 的 peer,然后自动获取 .torrent 的 info 部分。

❓ 什么是 InfoHash(哈希)?

  • 它是 .torrent 文件中 info 字段的 SHA1 哈希值(20字节,160位),唯一代表一个资源。
  • 意义:只要你和别人有相同的 infohash,你们就下载的是同一份文件。
  • BT 网络的基础就是“内容寻址”,即按内容而不是位置下载。

🔍 客户端解析后做了什么?

点击磁力链接后,BT 客户端(如 qBittorrent、BitComet、Transmission)会进行以下解析:

  1. 提取 infohash
    • magnet:?xt=urn:btih:<HASH> 中提取出资源唯一标识符 ABCDEF1234567890ABCDEF1234567890ABCDEF12
  2. 将 infohash 加入等待查询队列
    • 这就相当于说:“我想找谁拥有这个资源”。
  3. 开始进入 DHT 网络寻找种子节点(peers)
    • 如果带了 tr=...,客户端会首先尝试 Tracker 服务器寻找其他 Peer。
    • 如果没有(或者 Tracker 无响应),就进入 DHT 网络。

🧠 小结这一步:

操作说明
用户点击 magnet 链接触发了客户端的连接建立逻辑
客户端提取 infohash得到唯一内容标识符
开始 DHT 查找节点不依赖服务器,直接询问其他用户:谁有这份文件?
若发现 Tracker 地址也会发请求试图快速获得 Peer 列表

💡下一步我们会详细讲:

  1. 设备 A 是如何加入 DHT 网络的?DHT 网络结构如何维护?(节点表、桶、Kademlia)
  2. 如何通过 DHT 查询 infohash → 获取 Peer 的公网 IP 地址和端口
  3. A 和 C 是如何打洞/连接/开始下载的?(连接协商、协议握手、BitField)

是否继续?你可以说「继续下一步」,或说「先补充 XXX」来定制节奏。

2) 寻找 peers(两大来源)

a. DHT(首要,去中心化)

  • 协议:KRPC(基于 UDP 的简单 RPC),常见实现是 BitTorrent 的 Kademlia 变体。

  • 常用 RPC:

    • get_peers:输入 infohash,返回若干 nodes(其它 DHT 节点)或直接返回 peers(IP:port
    • announce_peer:告诉 DHT 节点“我有这个 infohash,可以提供”
  • 工作方式(高层):

    1. 客户端先向已知的 bootstrap 节点查询(如 router.bittorrent.com 等)以加入 DHT。
    2. 对 infohash 发起 get_peers,逐步查找直到找到接近 infohash 的节点或实际的 peers。
  • 消息示例(伪):

    request: {"t":"aa","y":"q","q":"get_peers","a":{"id":<nodeid>,"info_hash":<infohash>}}
    response: {"t":"aa","y":"r","r":{"nodes":..., "values":[["1.2.3.4",6881], ...]}}

b. Tracker(集中式,可选)

  • HTTP tracker:客户端做 GET /announce?info_hash=...&peer_id=...&port=...
  • UDP tracker:用 UDP 协议更轻量(BEP‑15/udp tracker)
  • Tracker 返回 peer 列表(IP:port
  • Tracker 也可作为最初的“bootstrap”来快速找到一些 peers

c. Peer Exchange (PEX)

  • 连接到一些 peers 后,他们会把自己知道的其他 peers 告诉你(PEX,BEP‑11/extend)
  • 这会快速扩大你可用的 peer 列表

3) 获取元数据(torrent metadata)

磁力链接没有包含 .torrent 的元数据信息(文件名、piece 大小、每块 hash 等),因此客户端需通过以下方式获得:

a. ut_metadata(BEP‑9)

  • 在与某个 peer 建立初步连接后,客户端用扩展消息 ut_metadata 请求对方发送 info 字段(按 piece 分片的 metadata)。
  • 消息流程(概念):
    1. 交换扩展握手(BEP‑10),表明支持 ut_metadata 和自己的 metadata_size。
    2. 发送 ut_metadata request(包含 piece index),peer 回应 data 消息,最终组装成完整的 torrent info
  • 只有当对等端正好已有该 torrent 的 metadata 才能用此法。

b. Webseeds / http seeds

  • 如果 magnet 包含 ws=as= 参数,可以用 HTTP 下载整个数据或 .torrent

4) 与 peer 建立连接(传输层与握手)

传输选择

  • TCP(经典 BitTorrent TCP 连接,默认端口通常 6881~6999)
  • uTP(基于 UDP 的微传输,拥塞友好) 客户端会尝试建立 TCP 或 uTP 连接到对方的 IP:port(由 DHT/Tracker/PEX 提供)。

BitTorrent 握手(第一个消息)

  • 固定格式(68 字节 + )

    <pstrlen=19><pstr="BitTorrent protocol"><reserved(8)><info_hash(20)><peer_id(20)>
  • 说明:

    • reserved 字段用于扩展位(如扩展协议、DHT、fast extensions)。
    • info_hash 用来确认双方在谈论同一个 torrent。
    • peer_id 用来标识客户端软件版本等(例如 -TR2920-... 是 Transmission)。

握手后的扩展握手(BEP‑10)

  • 握手后若 reserved 指示支持扩展协议,一般接着发送 “extension handshake” 的 bencoded 字典,列出客户端支持的扩展名(比如 ut_metadata, ut_pex)与对应的消息号。

5) 连接建立后 — metadata 完成 → piece 交换

piece 协议(经典消息)

  • 常见消息类型(在 TCP/uTP 的 BitTorrent 消息层):
    • choke / unchoke(用于流量控制)
    • interested / not interested
    • have(告知某块已拥有)
    • request(请求某个块)
    • piece(传输块数据)
    • cancel
  • 典型策略:
    • Rarest‑first:优先请求网络上最少的块,保证稀有块不消失
    • Tit‑for‑tat:基于上传回报的策略(优先给回报好的 peers 上传)
    • Optimistic unchoke:定期给一个新 peer 机会,提高发现好对等体的概率
    • Endgame mode:快完成时向多个 peers 重复请求最后几块以加速完成

6) NAT 问题与穿透(在 BitTorrent 世界是怎样应对的)

  • 大多数客户端都启动一个监听端口(TCP 端口或 UDP uTP 端口),并在启动时向外发起连接或向 tracker/DHT/peers 注册自己的地址(announce 或 announce_peer)。
  • NAT 的影响
    • 即使在 NAT 后,多数客户端仍能发起出站连接到其他 peers,因此 P2P 下载通常仍能进行(因为至少一方能建立出站到另一方的 TCP/uTP)。
    • Symmetric NAT 对 P2P 最不友好:映射不能复用,打洞难度大。BitTorrent 通过大量 peer 以及使用 uTP/UDP 与同时向多 peer 发起连接来提高命中概率;如果都不行,某些客户端/私有 tracker 或 ISP 会提供 relay(不常见)。
  • DHT + PEX 的优势:许多 peers、频繁互换,能迅速找到能和你直接连接上的 peers;你不太依赖单一 tracker。

典型报文/消息示例(简化版)

Tracker HTTP announce(简化版)

GET /announce?info_hash=%20%AF%...&peer_id=-AZ2060-6wfG2wk6wWLc&port=6881&uploaded=0&downloaded=0&left=123456&compact=1

Tracker 返回 bencoded 的 peers 列表(compact 格式为二进制 IP:port)。

BitTorrent 握手(字节流)

13 'B' 'i' 't' 'T' 'o' 'r' 'r' 'e' 'n' 't' ' ' 'p' 'r' 'o' 't' 'o' 'c' 'o' 'l'
<8 bytes reserved>
<20 bytes info_hash>
<20 bytes peer_id>

扩展握手(bencoded)

d1:md11:ut_metadata2:2e
1: m: { 'ut_metadata': 2, 'ut_pex': 1 } ...
e

ut_metadata 请求/数据

  • 客户端发 msg_type=0(request)加上 piece 索引
  • 对端回复 msg_type=1(data)并附上分片数据,最后有 msg_type=2(reject)可用

安全性、攻击与抗性

  • DHT poisoning / Sybil 攻击:恶意节点可返回假 peers(BEP 有所缓解)。
  • Torrent 劫持 / 假元数据:恶意节点可能提供错误 metadata;客户端应验证 infohash(metadata 的 SHA1)来检测伪造。
  • 版权/法律:很多内容受版权保护,公开分享可能违法(法律问题需注意)。

现实中常见的扩展/变体

  • BEP‑47/BS Tracking:增强隐私/匿名的方案(非主流)
  • uTP(Micro Transport Protocol, BEP‑29)用于替代 TCP 的数据传输,拥塞控制更好
  • IPv6 支持:随 IPv6 推广,直接 IPv6 对等通信越来越常见(无 NAT,穿透更容易)
  • 磁力 + DHT 为主流:如今很多 torrent 站点只给 magnet,客户端靠 DHT/PEX 完成。

工程实现角度(客户端的工作清单)

  1. 解析 magnet,拿到 infohash 并把任务加入下载队列
  2. 启动 DHT 节点(或使用系统 DHT),向 bootstrap 节点查询 get_peers(infohash)
  3. 同时向 magnet 中的 tr= trackers 发 announce(可选)
  4. 收到 peer 列表 → 尝试连接(TCP/uTP),发送 BitTorrent 握手
  5. 握手成功后,交换扩展信息,若缺 metadata 则使用 ut_metadata 请求
  6. metadata 获得后构建 piece 列表并开始下载(稀缺优先、tit‑for‑tat)
  7. 当下载完成后 announce completed,并继续作种(上传)

想看实际抓包或示例代码吗?

我可以:

  • 给你一段实际的 BitTorrent 握手字节流示例;
  • libtorrent / aria2 / transmission 的调用演示 magnet 下载全过程;
  • 或者用 scapy/wireshark 分析一次真实的 magnet → DHT get_peers → 握手 → ut_metadata 的抓包流程。

你想要哪一种更深入的演示?(抓包示例、代码片段,或把某个 BEP 的消息字段逐字节拆解)

流程如下:

  1. Peer 发现
  • 使用 Tracker 或 DHT(分布式哈希表)找到其它 peer
  1. 建立连接
  • 使用 NAT 穿透技巧建立 TCP/UDP 连接
  1. 传输数据
  • 支持分块、重传、冗余备份等

举个例子:BT 下载过程

你下载一个电影种子 →
通过 Tracker 或 DHT 找到其他正在下载/上传的人 →
直接与他们建立连接(P2P) →
分片下载,每个 peer 给你不同部分 →
你也把自己已有的部分发给其他 peer →
整个网络协作完成下载

很多游戏加速器是否就是使用这个原理?比如我在美国,要玩国内的游戏,正常延时在 310 ms ,而开启游戏加速器之后的延时降低到了 165ms。而我ping 一台在 国内的云服务器,延迟都有 300 ms